/** ****************************************************************************
 * CopyRight (C) 2012 Sharadendu Sinha.
 * Licensing : FreeBSD 
 *
 * @(#) $Id$
 */
package com.sps.signalling;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;

import com.sps.signalling.Invokable.Type;
import com.sps.signalling.exceptions.SignallingException;

/**
 * This is the base class that is needed to be extended from to use jsignalling
 * features. This class implements the {@link SlotProvider} ,
 * {@link SignalEmmiter} and {@link Executor} interfaces.
 * 
 * @version 1.1
 * @author sharadendu
 */
public class SignallingEntity
	implements SignalEmmiter, SlotProvider, Executor
{
	/** {@link HashMap} containing name -> {@link Invokable} mappings */
	private final Map<String, Invokable>	invokablesMap	= new HashMap<String, Invokable>();
	/**
	 * {@link Executor}s for slot invocation, by default Executor
	 * {@link SignallingEntity} will be used for both
	 * {@link com.sps.signalling.InvokableConnection.Type#THREADED} and
	 * {@link com.sps.signalling.InvokableConnection.Type#DIRECT}
	 */
	private Executor[]						executors		= new Executor[]
															{ this, this };

	/**
	 * Provides a setter for setting a custom {@link Executor} for
	 * {@link com.sps.signalling.InvokableConnection.Type#THREADED}. Allowing
	 * the invocation of {@link Slot} in a separate Thread
	 * 
	 * @param executor
	 ****************************************************************************/
	public void setThreadedExecutor(Executor executor)
	{
		if(executor == null)
			throw new SignallingException(SignallingException.INVALID_EXECUTOR);
		executors[InvokableConnection.Type.THREADED.ordinal()] = executor;
	}

	/**
	 * @see com.sps.signalling.InvokableProvider#addInvokable(com.sps.signalling.Invokable)
	 */
	@Override
	public void addInvokable(Invokable invokable)
	{
		invokablesMap.put(invokable.getName(), invokable);
	}

	/**
	 * @see com.sps.signalling.InvokableProvider#getInvokableByName(java.lang.String)
	 */
	@Override
	public Invokable getInvokableByName(String name) throws SignallingException
	{
		Invokable invokable = invokablesMap.get(name);
		if(invokable == null)
		{
			throw new SignallingException(name
				+ SignallingException.SIGNAL_NOT_FOUND + this);
		}
		return invokable;
	}

	/**
	 * @see com.sps.signalling.InvokableProvider#getInvokableByName(com.sps.signalling.InvokableProvider,
	 *      java.lang.String)
	 */
	@Override
	public Invokable getInvokableByName(InvokableProvider invokableProvider,
		String name) throws SignallingException
	{
		return invokableProvider.getInvokableByName(name);
	}

	/**
	 * @see com.sps.signalling.InvokableProvider#getInvokables(com.sps.signalling.Invokable.Type)
	 */
	@Override
	public List<Invokable> getInvokables(Type type)
	{
		List<Invokable> invokables = null;
		if(!invokablesMap.isEmpty())
		{
			for(String invokableName : invokablesMap.keySet())
			{
				Invokable invokable = invokablesMap.get(invokableName);
				if(invokable.getType() == type)
				{
					if(invokables == null)
					{
						invokables = new ArrayList<Invokable>();
					}
					invokables.add(invokable);
				}
			}
		}
		return invokables;
	}

	/**
	 * @see com.sps.signalling.SlotProvider#getExecutor(com.sps.signalling.InvokableConnection.Type)
	 */
	@Override
	public Executor getExecutor(com.sps.signalling.InvokableConnection.Type type)
	{
		return executors[type.ordinal()];
	}

	/**
	 * @see com.sps.signalling.SignalEmmiter#emit(com.sps.signalling.Signal,
	 *      java.lang.Object[])
	 */
	@Override
	public void emit(Signal signal, Object... paramObjects)
	{
		List<InvokableConnection> connections = signal.getConnections();
		for(InvokableConnection connection : connections)
		{
			connection.getDestinationInvokable().invoke(connection.getType(),
				paramObjects);
		}
	}

	/**
	 * @see java.util.concurrent.Executor#execute(java.lang.Runnable)
	 */
	@Override
	public void execute(Runnable command)
	{
		command.run();
	}

	/**
	 * @see com.sps.signalling.SignalEmmiter#connect(com.sps.signalling.Invokable,
	 *      com.sps.signalling.Invokable,
	 *      com.sps.signalling.InvokableConnection.Type)
	 */
	@Override
	public void connect(Invokable signal, Invokable invokable,
		com.sps.signalling.InvokableConnection.Type connectionType)
	{
		InvokableConnection connection = signal.getConnection(signal,
			invokable, connectionType);
		if(connection == null)
		{
			if(!(signal instanceof Signal))
			{
				throw new SignallingException(
					SignallingException.INVALID_SOURCE);
			}
			connection = new InvokableConnectionImpl((Signal)signal, invokable,
				connectionType);
		}
		signal.addConnection(connection);
	}

	/**
	 * @see com.sps.signalling.SignalEmmiter#connect(com.sps.signalling.SignalEmmiter,
	 *      java.lang.String, com.sps.signalling.Invokable,
	 *      com.sps.signalling.InvokableConnection.Type)
	 */
	@Override
	public void connect(SignalEmmiter signalEmmitter, String signalName,
		Invokable invokable,
		com.sps.signalling.InvokableConnection.Type connectionType)
		throws SignallingException
	{
		Invokable signal = signalEmmitter.getInvokableByName(signalName);
		signalEmmitter.connect(signal, invokable, connectionType);
	}

	/**
	 * @see com.sps.signalling.SignalEmmiter#connect(com.sps.signalling.Invokable,
	 *      com.sps.signalling.Invokable)
	 */
	@Override
	public void connect(Invokable signal, Invokable invokable)
	{
		connect(signal, invokable, InvokableConnection.Type.DIRECT);
	}

	/**
	 * @see com.sps.signalling.SignalEmmiter#connect(com.sps.signalling.SignalEmmiter,
	 *      java.lang.String, com.sps.signalling.Invokable)
	 */
	@Override
	public void connect(SignalEmmiter signalEmmitter, String signalName,
		Invokable invokable) throws SignallingException
	{
		signalEmmitter.connect(signalEmmitter, signalName, invokable,
			InvokableConnection.Type.DIRECT);
	}

	/**
	 * @see com.sps.signalling.SignalEmmiter#discconnect(com.sps.signalling.Invokable,
	 *      com.sps.signalling.Invokable)
	 */
	@Override
	public void discconnect(Invokable signal, Invokable invokable)
		throws SignallingException
	{
		discconnect(signal, invokable,
			com.sps.signalling.InvokableConnection.Type.DIRECT);
	}

	/**
	 * @see com.sps.signalling.SignalEmmiter#discconnect(com.sps.signalling.SignalEmmiter,
	 *      java.lang.String, com.sps.signalling.Invokable)
	 */
	@Override
	public void discconnect(SignalEmmiter signalEmmitter, String signalName,
		Invokable invokable) throws SignallingException
	{
		signalEmmitter.discconnect(
			signalEmmitter.getInvokableByName(signalName), invokable);
	}

	/**
	 * @see com.sps.signalling.SignalEmmiter#discconnect(com.sps.signalling.Invokable,
	 *      com.sps.signalling.Invokable,
	 *      com.sps.signalling.InvokableConnection.Type)
	 */
	@Override
	public void discconnect(Invokable signal, Invokable invokable,
		com.sps.signalling.InvokableConnection.Type type)
		throws SignallingException
	{
		InvokableConnection connection = signal.getConnection(signal,
			invokable, type);
		if(connection == null)
		{
			throw new SignallingException(
				SignallingException.CONNECTION_NOT_FOUND);
		}
		signal.removeConnection(connection);
	}

	/**
	 * @see com.sps.signalling.SignalEmmiter#discconnect(com.sps.signalling.SignalEmmiter,
	 *      java.lang.String, com.sps.signalling.Invokable,
	 *      com.sps.signalling.InvokableConnection.Type)
	 */
	@Override
	public void discconnect(SignalEmmiter signalEmmiter, String signalName,
		Invokable invokable, com.sps.signalling.InvokableConnection.Type type)
		throws SignallingException
	{
		Invokable signal = signalEmmiter.getInvokableByName(signalName);
		signalEmmiter.discconnect(signal, invokable, type);
	}

	/**
	 * @see com.sps.signalling.SignalEmmiter#connect(java.lang.String,
	 *      com.sps.signalling.Invokable)
	 */
	@Override
	public void connect(String signalName, Invokable invokable)
		throws SignallingException
	{
		connect(getInvokableByName(signalName), invokable);
	}

	/**
	 * @see com.sps.signalling.SignalEmmiter#connect(java.lang.String,
	 *      com.sps.signalling.Invokable,
	 *      com.sps.signalling.InvokableConnection.Type)
	 */
	@Override
	public void connect(String signalName, Invokable invokable,
		com.sps.signalling.InvokableConnection.Type type)
		throws SignallingException
	{
		connect(getInvokableByName(signalName), invokable, type);
	}

	/**
	 * @see com.sps.signalling.SignalEmmiter#discconnect(java.lang.String,
	 *      com.sps.signalling.Invokable)
	 */
	@Override
	public void discconnect(String signalName, Invokable invokable)
		throws SignallingException
	{
		discconnect(getInvokableByName(signalName), invokable);
	}

	/**
	 * @see com.sps.signalling.SignalEmmiter#discconnect(java.lang.String,
	 *      com.sps.signalling.Invokable,
	 *      com.sps.signalling.InvokableConnection.Type)
	 */
	@Override
	public void discconnect(String signalName, Invokable invokable,
		com.sps.signalling.InvokableConnection.Type type)
		throws SignallingException
	{
		discconnect(getInvokableByName(signalName), invokable, type);
	}

	/**
	 * @see com.sps.signalling.SignalEmmiter#emit(java.lang.String, java.lang.Object[])
	*/
	@Override
	public void emit(String signalName, Object... paramObjects)
		throws SignallingException
	{
		emit((Signal)getInvokableByName(signalName),paramObjects);
	}
}
